package qj.ac.context;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;

import qj.util.ReflectUtil;
import qj.util.funct.P0;

public class BeanPopulator {

	LinkedList<BeanH> beans = new LinkedList<BeanH>();
	private P0 populateContextF;
	

	public void populateContainer(Object container) {
		populateContainer(container, false);
	}
	public void populateContainerForce(Object container) {
		populateContainer(container, true);
	}
	public void populateContainer(Object container, boolean force) {
		for (Field field : ReflectUtil.getAllFields(container.getClass()).values()) {

			if ((field.getModifiers() & Modifier.PUBLIC) == 0) {
				continue;
			}
			
			Object bean = ReflectUtil.getFieldValue(field, container);
			
			if (bean==null) {
				continue;
			}
			populateBean(bean, force);
		}
	}

	public <A> A populateBean(A bean) {
		return populateBean(bean, false);
	}

	public <A> A populateBean(A bean, boolean finish) {

		for (Field field : ReflectUtil.getAllFields(bean.getClass()).values()) {

			if ((field.getModifiers() & Modifier.PUBLIC) ==0) {
				continue;
			}
			
			Wired wired = field.getAnnotation(Wired.class);
			if (wired==null ||
					ReflectUtil.getFieldValue(field, bean) != null) {
				continue;
			}
			
			Object beanComponent = getBean(
					wired.value().length()==0 ? field.getName() : wired.value(),
					field.getType(),
					wired.forced());
			if (beanComponent==null && finish) {
				throw new RuntimeException("Can not wire " + field.getName() + "(" + field.getType() + ") in bean " + bean.getClass());
			}
			ReflectUtil.setFieldValue(beanComponent, field, bean);
		}

		if (finish) {
			for (Method method : ReflectUtil.getAllMethods(bean.getClass()).values()) {
				if (method.isAnnotationPresent(AfterWired.class)) {
//					
//					if ((method.getModifiers() & Modifier.PUBLIC) == 0 ||
//							method.getParameterTypes().length > 0) {
//						continue;
//					}
					ReflectUtil.invoke(method, bean, new Object[]{});
				}
			}
		}
		
		return bean;
	}
	
	public void invokeAfterWiredMethods(Object bean) {
		for (Method method : ReflectUtil.getAllMethods(bean.getClass()).values()) {
			if (method.isAnnotationPresent(AfterWired.class)) {
				ReflectUtil.invoke(method, bean, new Object[]{});
			}
		}
	}

	@SuppressWarnings("unchecked")
	public <A> A getBean(String name, Class<A> clazz) {
		for (BeanH bean : beans) {
			if (bean.name != null && bean.name.equals(name) && clazz.isAssignableFrom(bean.bean.getClass())) {
				return (A) bean.bean;
			}
		}
		for (BeanH bean : beans) {
			if (clazz.isAssignableFrom(bean.bean.getClass())) {
				return (A) bean.bean;
			}
		}
		// TODO create bean
		return null;
	}

	public void add(Object value) {
		beans.add(new BeanH(null, value));
	}
	public void add(String name, Object value) {
		beans.add(new BeanH(name, value));
	}

	static class BeanH {
		String name;
		Object bean;
		public BeanH(String name, Object bean) {
			this.name = name;
			this.bean = bean;
		}
	}

	public static BeanPopulator fromContext(final Object context) {
		final BeanPopulator beanPopulator = new BeanPopulator();
		
		beanPopulator.populateContextF = new P0() {public void e() {
			beanPopulator.populateContainerForce(context);
		}};
		
		beanPopulator.add(null, beanPopulator);
		beanPopulator.add(null, context);
		for (Field field : context.getClass().getDeclaredFields()) {

			if ((field.getModifiers() & Modifier.PUBLIC) ==0) {
				continue;
			}
			
			Object value = ReflectUtil.getFieldValue(field, context);
			if (value != null) {
				beanPopulator.add(field.getName(), value);
			}
		}
		return beanPopulator;
	}

	public void populateContext() {
		populateContextF.e();
	}

	public <A> A create(Class<A> clazz) {
		A obj = ReflectUtil.newInstance(clazz);
		return populateBean(obj);
	}

	public <A> A getBean(String name, Class<A> clazz, boolean force) {
		return force ? getForce(name, clazz) : getBean(name, clazz);
	}

	public <A> A getForce(String name, Class<A> clazz) {
		A bean = getBean(name, clazz);
		if ( bean == null ) {
			bean = createStore(clazz);
			add(name, bean);
			return bean;
		} else {
			return bean;
		}
	}
	
	public <A> A createStore(Class<A> clazz) {
		A obj = ReflectUtil.newInstance(clazz);
		populateBean(obj);
		add(obj);
		return obj;
	}
}
